/*
 * component:   "openEHR Java Reference Implementation"
 * description: "Class Flattener"
 * keywords:    "oet-parser"
 *
 * author:      "Rong Chen <rong.acode@gmail.com>"
 * copyright:   "Copyright (c) 2009,2010 Cambio Healthcare Systems, Sweden"
 * license:     "See notice at bottom of class"
 *
 * file:        "$URL$"
 * revision:    "$LastChangedRevision$"
 * last_change: "$LastChangedDate$"
 */

package org.openehr.am.template;

import java.math.BigInteger;
import java.text.DecimalFormat;
import java.util.*;

import org.apache.log4j.Logger;

import openEHR.v1.template.ACTION;
import openEHR.v1.template.ADMINENTRY;
import openEHR.v1.template.Archetyped;
import openEHR.v1.template.COMPOSITION;
import openEHR.v1.template.ContentItem;
import openEHR.v1.template.ENTRY;
import openEHR.v1.template.EVALUATION;
import openEHR.v1.template.INSTRUCTION;
import openEHR.v1.template.ITEMSTRUCTURE;
import openEHR.v1.template.OBSERVATION;
import openEHR.v1.template.QuantityConstraint;
import openEHR.v1.template.QuantityUnitConstraint;
import openEHR.v1.template.SECTION;
import openEHR.v1.template.TEMPLATE;
import openEHR.v1.template.Statement;
import openEHR.v1.template.TextConstraint;
import openEHR.v1.template.ValueConstraint;
import openEHR.v1.template.MultipleConstraint;
import openEHR.v1.template.ITEM;

import org.openehr.am.archetype.Archetype;
import org.openehr.am.archetype.constraintmodel.*;
import org.openehr.am.archetype.constraintmodel.CAttribute.Existence;
import org.openehr.am.archetype.constraintmodel.primitive.CString;
import org.openehr.am.openehrprofile.datatypes.quantity.CDvQuantity;
import org.openehr.am.openehrprofile.datatypes.quantity.CDvQuantityItem;
import org.openehr.am.openehrprofile.datatypes.text.CCodePhrase;
import org.openehr.rm.common.archetyped.Locatable;
import org.openehr.rm.datatypes.text.CodePhrase;
import org.openehr.rm.support.basic.Interval;

/**
 * A template flattener which can take OET object model generated by 
 * OET-template parser and join constraints from the parent archetype and
 * all included children archetypes, and output the combined constraints
 * in pure AOM instance.
 * 
 * @author rong.chen
 *
 */
public class Flattener {
	
	/**
	 * Constructor
	 */
	public Flattener() {
		archetypeMap = new HashMap<String, Archetype>();		
		termMap = new TermMap();
	}
	
	/**
	 * Flatten given template only with archetypes
	 * 
	 * @param template
	 * @param archetypeMap
	 * @return
	 * @throws Exception
	 */
	public Archetype toFlattenedArchetype(TEMPLATE template, 
			Map<String, Archetype> archetypeMap) throws FlatteningException {
		
		return toFlattenedArchetype(template, archetypeMap, 
									new HashMap<String, TEMPLATE>());
	}
	
	/**
	 * Flatten given template with referenced archetypes and sub-templates
	 * 
	 * @param template
	 * @param archetypeMap    not null
	 * @param templateMap   null if unspecified
	 * @return
	 * @throws FlatteningException if null archetypeMap
	 */
	public Archetype toFlattenedArchetype(TEMPLATE template, 
			Map<String, Archetype> archetypeMap,  
			Map<String, TEMPLATE> templateMap) throws FlatteningException {		
		
		if(archetypeMap == null) {
			throw new FlatteningException("null archetypeMap");
		}		
		
		// make a internal copy of archetypeMap
		this.archetypeMap.clear();
		for(String id : archetypeMap.keySet()) {
			Archetype a = archetypeMap.get(id);
			if(a != null) {
				a = a.copy();
				this.archetypeMap.put(id, a);
			}
		}
		// no need to copy templateMap
		this.templateMap = templateMap;
		
		log.debug("Loaded archetype/template maps, total archetypes: " + 
				archetypeMap.size() + ", total templates: " + 
				(templateMap == null ? 0 : templateMap.size()));		
		
		return toFlattenedArchetype(template);
	}
	
	/*
	 * Only used when archetype/template maps are initialized!
	 */
	private Archetype toFlattenedArchetype(TEMPLATE template) 
			throws FlatteningException {
		
		log.debug("Flattening template.. STARTED");
		
		Archetyped definition = template.getDefinition();		
		
		// start flattening
		Archetype flattended = flattenArchetyped(definition);		
		flattended.reloadNodeMaps();
		
		log.debug("Flattening template DONE");
		
		return flattended;
	}
	
	/**
	 * Flattening entry point for root class archetyped
	 * 
	 * @param parentArchetype provides the context of flattening, null for first
	 * entry of the recursive function	   
	 * @param definition
	 * @throws FlatteningException
	 */
	private Archetype flattenArchetyped(Archetyped definition) throws FlatteningException {
		
		log.debug("flattening archetyped on archetype: " + definition.getArchetypeId());
		
		// TODO handle template_id
		
		
		if(definition instanceof COMPOSITION) {
	
			// only exception when parentArchetype is always not set
			return flattenComposition((COMPOSITION) definition);
		
		} else if(definition instanceof ITEMSTRUCTURE) {
		
			return flattenItemStructure(null, (ITEMSTRUCTURE) definition);
		
		} else if(definition instanceof ContentItem) {
		
			return flattenContentItem(null, (ContentItem) definition);
		
		} else if(definition instanceof ITEM) {
		
			return flattenItem(null, (ITEM) definition); 
		
		} else {
			throw new FlatteningException("Unkown archetyped sub-type");
		}
	}
	
	/*
	 * Flatten an item
	 */
	private Archetype flattenItem(Archetype parentArchetype, ITEM item) 
		throws FlatteningException {
		
		log.debug("flattening item on parentArchetype " + 
				parentArchetype.getArchetypeId() + " at path " + 
				item.getPath());
		
		Archetype archetype = retrieveArchetype(item.getArchetypeId());
		
		applyTemplateConstraints(archetype, item);
		
		fillArchetypeSlot(parentArchetype, archetype, item.getPath(), 
				item.getName());
		
		return archetype;
	}
	
	private Archetype flattenComposition(COMPOSITION composition) throws FlatteningException {
		
		log.debug("flattening composition on archetype: " + composition.getArchetypeId());
		
		Archetype archetype = retrieveArchetype(composition.getArchetypeId());
		CComplexObject root = archetype.getDefinition();
		CAttribute contentAttribute = root.getAttribute(CONTENT);		
		
		removeArchetypeSlots(contentAttribute);
		
		// handle "/content" attribute
		ContentItem[] items = composition.getContentArray();
		if(items != null && items.length > 0) {
			String path = "/" + CONTENT; 
			
			if(contentAttribute == null) {
				List<CObject> alternatives = new ArrayList<CObject>();
				contentAttribute = new CMultipleAttribute(path, CONTENT, 
						Existence.OPTIONAL,	Cardinality.LIST, alternatives);
				archetype.getDefinition().addAttribute(contentAttribute);
			}
			
			for(ContentItem item : items) {
				
				log.debug("flattening composition.content..");
				
				flattenContentItem(archetype, item);								
			}
		}		
		
		// TODO handle "/context" attribute	
		// log.warn("flattening composition.context..not implemented");
		
		applyRules(archetype, composition.getRuleArray());
		applyNameConstraint(archetype, archetype.getDefinition(), 
				composition.getName(), "/");
		
		return archetype;
	}
	
	private Archetype flattenContentItem(Archetype parentArchetype, 
			ContentItem definition)	throws FlatteningException {
		
		log.debug("flattening content_item on archetype: " + 
				definition.getArchetypeId() + " on path: " + definition.getPath());
		
		Archetype archetype = null;
		String templateId = definition.getTemplateId();
		
		if(templateId == null) {
		
			archetype = retrieveArchetype(definition.getArchetypeId());
		
		} else {
		
			// TODO only needed at SECTION level so far for ifk2
			TEMPLATE template = retrieveTemplate(templateId);
			archetype = toFlattenedArchetype(template);
		}
		
		applyTemplateConstraints(archetype, definition);
		
		if(definition instanceof ENTRY) {
	
			flattenEntry(parentArchetype, archetype, (ENTRY) definition);
		
		} else if(definition instanceof SECTION) {
		
			flattenSection(parentArchetype, archetype, (SECTION) definition);
			
		} else {
			throw new FlatteningException(
					"Unexpected subtype of ContentItem: " + definition);
		}		
		return archetype;
	}	
	
	/*
	 * Apply common template constraints
	 */
	private void applyTemplateConstraints(Archetype archetype, 
			Archetyped definition) throws FlatteningException {
		
		log.debug("applying common template constraints.. ");
		
		if(archetype == null) {
			return;
		}
		
		String name = null;
		Statement[] rules = null;
		BigInteger max = null;
		BigInteger min = null;
		boolean hideOnForm = false;
		String annotation = null;
		
		if(definition instanceof ContentItem) {
			
			ContentItem item = (ContentItem) definition;
			name = item.getName();
			rules = item.getRuleArray();
			max = item.getMax();
			min = item.getMin();
			hideOnForm = item.getHideOnForm();
			annotation = item.getAnnotation();
			
		} else if(definition instanceof ITEMSTRUCTURE) {
			
			ITEMSTRUCTURE item = (ITEMSTRUCTURE) definition;
			name = item.getName();
			rules = item.getRuleArray();
			max = item.getMax();
			min = item.getMin();
			hideOnForm = item.getHideOnForm();
			annotation = item.getAnnotation();
			
		} else if(definition instanceof ITEM) {
			
			ITEM item = (ITEM) definition;
			name = item.getName();
			rules = item.getRuleArray();
			max = item.getMax();
			min = item.getMin();
			hideOnForm = item.getHideOnForm();
			annotation = item.getAnnotation();			
		
		} else {
			log.warn("unsupported definition type: " + definition);
		}
		
		applyNameConstraint(archetype, archetype.getDefinition(), name , "/");
		
		applyRules(archetype, rules);
		
		applyOccurrencesConstraint(archetype, archetype.getDefinition(), max, min);
		
		applyHideOnFormConstraint(archetype.getDefinition(), hideOnForm);
		
		applyAnnotationConstraint(archetype.getDefinition(), annotation);
	}
	
	private void setPathPrefixOnCObjectTree(CObject cobj, String prefix) 
			throws FlatteningException {
		
		// TODO shouldn't happen
		if(cobj == null) {
			log.warn("null cobj encountered at setPathPrefix(): " + prefix);
			return;
		}
		
		String path = cobj.path();
		if("/".equals(path)) {
			path = prefix;
		} else {
			path = prefix + path;
		}
		cobj.setPath(path);
		
		if(path.endsWith(VALUE)) {
		//	log.debug("setPathPrefixOnCObjectTree - value path: " + path);
		}		
		
		if(cobj instanceof CComplexObject) {
			CComplexObject ccobj = (CComplexObject) cobj;
			for(CAttribute cattr : ccobj.getAttributes()) {
				path = cattr.path();
				cattr.setPath(prefix + path);
				for(Iterator<CObject> it = cattr.getChildren().iterator();
						it.hasNext();) {
				
					CObject child = it.next();					
					// TODO
					if(child == null) {
						it.remove();
						log.warn("null child encountered remove in setPathPrefix()..");
					}
					setPathPrefixOnCObjectTree(child, prefix);					
				}
			}
		}		
	}
	
	private void updatePathWithNamedNodeOnCObjectTree(CObject cobj, 
			String nodeId, String name)	throws FlatteningException {

		// TODO shouldn't happen
		if (cobj == null) {
			log.warn("null cobj in updatePathWithNamedNodeOnCObjectTree(): " 
					+ nodeId + "/" + name);
			return;
		}
		
		String path = cobj.path();		
		path = replaceNodeIdWithNamedNode(path, nodeId, name);
		cobj.setPath(path);
		
		//log.debug("cobj.path: " + cobj.path());
		
		if (cobj instanceof CComplexObject) {
			CComplexObject ccobj = (CComplexObject) cobj;
		
			for (CAttribute cattr : ccobj.getAttributes()) {
		
				path = cattr.path();
				path = replaceNodeIdWithNamedNode(path, nodeId, name);				
				cattr.setPath(path);
				
				//log.debug("cattr.path: " + cattr.path());
				
				for (Iterator<CObject> it = cattr.getChildren().iterator(); it
						.hasNext();) {

					CObject child = it.next();
					// TODO
					if (child == null) {
						it.remove();
						log.warn("null child encountered remove in setPathPrefix()..");
					}
					updatePathWithNamedNodeOnCObjectTree(child, nodeId, name);
				}
			}
		}
	}
	
	private String replaceNodeIdWithNamedNode(String path, String nodeId, 
			String name) {
		return path.replaceFirst("\\[" + nodeId + "\\]", 
				"\\[" + namedNodeSegment(nodeId, name) + "\\]");
	}
	
	private String namedNodeSegment(String nodeId, String name) {
		return nodeId + " and name/value='" + name + "'";
	}
	
	private Archetype flattenSection(Archetype parentArchetype, 
			Archetype archetype, SECTION section) throws FlatteningException {
		
		log.debug("flattening section on archetype " + section.getArchetypeId() 
				+ " at path " + section.getPath());
		
		ContentItem[] items = section.getItemArray();
		String path = section.getPath();		
		CComplexObject root = archetype.getDefinition();
		
		fillArchetypeSlot(parentArchetype, archetype, path, section.getName());
				
		CAttribute itemsAttribute = root.getAttribute(ITEMS);
		
		// handle SECTION.items		
		if(items != null && items.length > 0) {			
			
			// TODO shouldn't be necessary if the archetype has
			// proper archetype_slots
			if(itemsAttribute == null) {
				
				if(parentArchetype == null) {
					path = ""; 
				} else {
					path = root.path(); 
				}
				path += "/" + ITEMS;
				
				List<CObject> alternatives = new ArrayList<CObject>();
				itemsAttribute = new CMultipleAttribute(path, ITEMS, 
						Existence.OPTIONAL,	Cardinality.LIST, alternatives);
				root.addAttribute(itemsAttribute);
			}			
			
			// flatten each item in the list
			for(ContentItem item : items) {
				
				log.debug("flattening section.items.. ");
				
				flattenContentItem(archetype, item);					
			}	
		}		
		return archetype;
	}
	
	// validity check of node_id (archetype_id) and name among siblings
	private void checkSiblingNodeIdAndName(CAttribute parent,
			String nodeId, String name) throws FlatteningException {
		
		for(CObject cobj : parent.getChildren()) {
			if(nodeId.equals(cobj.path()) && name.equals(cobj.getNodeId())) {
				throw new FlatteningException("duplicated node_id/name: " +
						nodeId + "/" + name + "at path: " + parent.path());
			}
		}
	}
	
	private int countChildOfArchetypeId(CAttribute cattr, String archetypeId) {
		int count = 0;
		for(CObject cobj : cattr.getChildren()) {
			String nodeId = cobj.getNodeId();
			if(nodeId == null) {
				continue;
			}
			if(nodeId.startsWith(archetypeId)) {
				count++;
			}
		}
		return count;
	}
	
	/**
	 * Overwrite recursively all the nodeIds in the given cobj tree with 
	 * incremental number starting with given count value
	 * 
	 * @param ccobj
	 * @param diff
	 * @return total number of nodeIds adjusted, used to update nodeId counter
	 */
	protected long adjustNodeIds(CObject cobj, long count) 
			throws FlatteningException {
		
		if(cobj.getNodeId() != null) {
			count++;
			cobj.setNodeId(formatNodeId(count));
		}
		if(cobj instanceof CComplexObject) {
			for(CAttribute attr : ((CComplexObject) cobj).getAttributes()) {
				for(CObject child : attr.getChildren()) {
					count = adjustNodeIds(child, count);
				}
			}
		}
		return count;
	}
	
	// TODO until this is in sync with pathNodeMap, this shouldn't be used
	protected void adjustNodeIds(CObject root) throws FlatteningException {
		this.nodeCount = adjustNodeIds(root, this.nodeCount);
	}
	
	// "/content" => "content"
	// "/content/name" => "name"
	// "/data[at0001]/items[at0002]" => "items"
	private String lastAttribute(String path) {
		int i = path.lastIndexOf("/");
		String attr = path.substring(i + 1, path.length());
		i = attr.indexOf("[");
		if(i >= 0) {
			attr = attr.substring(0, i);
		}
		log.debug("attribute name: " + attr);
		return attr;
	}
	
	private Archetype flattenEntry(Archetype parentArchetype, 
			Archetype archetype, ENTRY definition) throws FlatteningException {
		
		log.debug("flattening entry on archetype: "	+ definition.getArchetypeId()
				+ ", path: " + ((ENTRY) definition).getPath());
				
		String path = definition.getPath();
				
		// bind current archetype to the parent 
		fillArchetypeSlot(parentArchetype, archetype, path, definition.getName());
		
		// common part for all entries
		ENTRY entry = (ENTRY) definition;
		ITEM[] items = entry.getItemsArray();
		if(items != null) {
			for(ITEM item : items) {				
				flattenItem(archetype, item);				
			}
		}
		
		// more specialized handling of sub-entry
		if(definition instanceof EVALUATION) {
			
			// no special handling
		
		} else if(definition instanceof OBSERVATION) {
			
			// no special handling
		
		} else if(definition instanceof ACTION) {
			
			// TODO handle description item
			ACTION action = (ACTION) definition;
			
			parentArchetype = retrieveArchetype(action.getArchetypeId());
			ITEMSTRUCTURE description = action.getDescription();
			if(description != null) {
				flattenItemStructure(archetype, description);
			}		
		
		} else if(definition instanceof INSTRUCTION) {
			
			flattenInstruction(parentArchetype, archetype, (INSTRUCTION) definition);
		
		} else if(definition instanceof ADMINENTRY) {
			
			// no special handling	
		}
				
		return archetype;
	}
	
	/**
	 * Fill archetype_slot at specified path of parentArchetype
	 * and remove all slots
	 * 
	 * @param parentArchetype
	 * @param archetype
	 * @param path
	 * @throws FlatteningException
	 */
	private void fillArchetypeSlot(Archetype parentArchetype, 
			Archetype archetype, String path, String name) throws FlatteningException {
		
		String archetypeId = archetype.getArchetypeId().toString();
		CComplexObject root = archetype.getDefinition();
		
		if(parentArchetype != null ) {
			
			// TODO has a quick-fix for the following path syntax
			// "/activities[at0001 and name/value='Medication activity']/description"
			// WILL NOT WORK IN OTHER CASES!!  
			
			int hybridStart = path.indexOf(" and name/value='");
			if( hybridStart > 0) {
				int i = path.indexOf("]");
				path = path.substring(0, hybridStart) + path.substring(i);
				
				log.debug("hybrid path detected, converted physical path: " + path);
			}			
			
			CAttribute attribute = getParentAttribute(parentArchetype, path);			
			if(attribute == null) {			
				throw new FlatteningException("CAttribute not found at " + path);		
			}			
			removeArchetypeSlots(attribute);
			
			root.setNodeId(archetypeId);
			String pathSegment = archetypeId;
			if(name != null) {
				checkSiblingNodeIdAndName(attribute, archetypeId, name);
				pathSegment = namedNodeSegment(archetypeId, name);
			}
			setPathPrefixOnCObjectTree(root, attribute.path() + "[" + 
					pathSegment + "]");
			
			attribute.addChild(root);		
		}
	}
	
	private CAttribute getParentAttribute(Archetype archetype, String path)
			throws FlatteningException	{
		
		String parentPath = Locatable.parentPath(path);
		ArchetypeConstraint ac = archetype.node(parentPath);
		
		if( ! (ac instanceof CComplexObject)) {
			throw new FlatteningException("Parent node not found at " + path +
					", computed parentPath: " + parentPath);
		}		
		CComplexObject parentNode = (CComplexObject) ac;
		String attributeName = lastAttribute(path);
		CAttribute attribute = parentNode.getAttribute(attributeName);
		return attribute;
	}
	
	private void flattenInstruction(Archetype parentArchetype, 
			Archetype archetype, INSTRUCTION instruction) 
			throws FlatteningException {
		
		log.debug("flattening instruction on archetype: "	+ instruction.getArchetypeId()
				+ ", path: " + ((ENTRY) instruction).getPath());
		
		ITEMSTRUCTURE[] descriptions = instruction.getActivityDescriptionArray();		
		
		if(descriptions != null) {					
			for(ITEMSTRUCTURE item : descriptions) {
				flattenItemStructure(archetype, item);			
			}
		}
	}
	
	private void removeArchetypeSlots(CAttribute cattr) {
		if(cattr == null) {
			return;
		}
		// TODO verify slots by RM types 
		for(Iterator<CObject> it = cattr.getChildren().iterator(); 
				it.hasNext();) {
			CObject cobj = it.next();
			if(cobj instanceof ArchetypeSlot) {
				it.remove();
				log.debug("archetype_slot removed from attribute " + 
						cattr.getRmAttributeName());			
			}
		}	
	}
	
	/*
	 * Flatten any subclass of item_structure
	 * 
	 * @param parentArchetype
	 * @param structure
	 * @return
	 * @throws FlatteningException
	 */
	private Archetype flattenItemStructure(Archetype parentArchetype,
			ITEMSTRUCTURE structure) throws FlatteningException {		
		
		log.debug("flattening item_structure on archetype: " +
				structure.getArchetypeId()	+ " on path: " + structure.getPath());
		
		Archetype archetype = retrieveArchetype(structure.getArchetypeId());
		
		applyTemplateConstraints(archetype, structure);
		
		fillArchetypeSlot(parentArchetype, archetype, structure.getPath(), 
				structure.getName());
		
		ITEM[] items = structure.getItemsArray();
		if(items != null) {
			for(ITEM item : items) {				
				flattenItem(archetype, item);				
			}
		}			
		return archetype;
	}
	
	/**
	 * Apply a set of rules in the context of given archetype
	 * 
	 * Important to keep passing the same archetype reference
	 * and keep the archetype updated when each rule is applied
	 * 
	 * @param archetype
	 * @param rules
	 * @throws FlatteningException
	 */
	void applyRules(Archetype archetype, Statement[] rules) 
			throws FlatteningException {
		
		if(rules == null) {
			return;
		}
		String name = null;
		String leadingPath = null;
		
		for(Statement rule : rules) {
			if(rule.getName() != null) {
				if(name == null) {
					name = rule.getName();
					leadingPath = rule.getPath();
					
				} else if(rule.getPath().equals(leadingPath)) {
					
					// more than one named node for the same path,
					// thus no need to rewrite the paths
					log.debug("more than one named node [" + name + 
							"] on path: " + leadingPath );
					
					name = null;					
					break;
				}
			}			
		}
		
		// rewrite rule path for named nodes
		// ASUMMING order is respected!!
		for(Statement rule : rules) {
			if(name != null && rule.getPath().startsWith(leadingPath)
					&& rule.getPath().length() > leadingPath.length()) {
				int len = leadingPath.length();
				String path = rule.getPath();
				path = path.substring(0, len - 1) + " and name/value='" +
					name + "'" + path.substring(len - 1);
				
				rule.setPath(path);
				
				log.debug("rewrote path with named node: " + path);
			}
			applyRule(archetype, rule);
		}
	}
	
	/**
	 * Apply a single rule on given archetype
	 * 
	 * @param archetype
	 * @param rule
	 * @throws FlatteningException
	 */
	void applyRule(Archetype archetype, Statement rule) 
			throws FlatteningException {
		
		log.debug("apply rule [" + rule + "] on archetype: " + 
				archetype.getArchetypeId().toString());
		
		String path = rule.getPath();
		
		ArchetypeConstraint constraint = archetype.node(rule.getPath());
		
		if(constraint == null) {
			throw new FlatteningException("no constraint on path: " 
					+ path + " of " + archetype.getArchetypeId());
		}		
		
		constraint = applyNameConstraint(archetype, constraint, rule.getName(),
				path);
		
		applyOccurrencesConstraints(archetype, constraint, rule);		
		
		applyDefaultValueConstraint(constraint, rule.getDefault());
		
		applyHideOnFormConstraint(constraint, rule.getHideOnForm());
		
		applyAnnotationConstraint(constraint, rule.getAnnotation());		
		
		applyValueConstraint(archetype, constraint, rule);	
		
		archetype.updatePathNodeMap((CObject) constraint);
		
		if(constraint instanceof CObject) {
			log.debug("newly set Occurrences: " + ((CObject) constraint).getOccurrences() );
		}	
		
	}	
	
	// TODO 
	protected void applyHideOnFormConstraint(ArchetypeConstraint constraint,
			boolean hideOnForm) {
		return;
	}
	
	// TODO 
	protected void applyAnnotationConstraint(ArchetypeConstraint constraint,
			String annotation) {
		if(annotation == null || annotation.length() == 0) {
			return;
		}
		constraint.setAnnotation(annotation);
	}
	
	/**
	 * Apply a default value rule constraint
	 * 
	 * Example:
	 *   <Rule path="/items[at0001]" default="Ipren" />
	 *   <Rule path="/items[at0001]" default="SNOMED-CT::258835005::mg/dygn" />	 * 
	 * 
	 * @param constraint
	 * @param rule
	 * @throws FlatteningException if rmType doesn't fit
	 */
	protected void applyDefaultValueConstraint(ArchetypeConstraint constraint,
			String defaultValue) throws FlatteningException {
		
		if(defaultValue == null) {
			return;
		}
		
		log.debug("applying default value on path: " + constraint.path());
		
		if( ! (constraint instanceof CComplexObject)) {
			
			throw new FlatteningException("failed to apply default constraint," +
					"unexpected constraint node: " + constraint.getClass());
		}	
		
		CComplexObject ccobj = (CComplexObject) constraint;
		
		if( ! ccobj.getRmTypeName().equalsIgnoreCase(ELEMENT)) {
			
			throw new FlatteningException("failed to apply default constraint," +
					"unexpected rmType[" + ccobj.getRmTypeName() + 
					"] on node: " + ccobj.path());
		}		
			
		// handles case like:
		// 
		// ELEMENT match {
		//     value match {
		//         DV_TEXT matches {*}
		//     }
		// }
		// or 
		// ELEMENT match {
		//     value match {
		//         DV_CODED_TEXT matches {*}
		//     }
		// }		
		// 
		// replace it with 
		// 		CPrimitiveObject.cString or CCodePhrase
		// 
		// TODO: an alternative is to use the term_definition 
		//       in archetype_ontology
		// 
		
		CAttribute cattr = ccobj.getAttribute(VALUE);
		
		if(cattr == null) {
			cattr = new CSingleAttribute(ccobj.path() + "/" + VALUE, 
			VALUE, Existence.REQUIRED); 
		
		} else if (cattr != null && cattr.getChildren().size() == 1) {					
			CObject child = cattr.getChildren().get(0);
			
			if(child instanceof CComplexObject) {
				CComplexObject childCCObj = (CComplexObject) child;
				String rmType = childCCObj.getRmTypeName();
				if(DV_TEXT.equals(rmType) || DV_CODED_TEXT.equals(rmType)) {
					cattr.removeChild(child);
				}
			}
		} 
		
		Interval<Integer> occurrences = new Interval<Integer>(1,1);
		
		// simple text value
		if(defaultValue.indexOf("::") < 0) {
			
			String path = cattr.path() + "/" + VALUE; 
			CComplexObject valueObj = CComplexObject.createSingleRequired(path, DV_TEXT);
			CAttribute valueAttr = CSingleAttribute.createRequired(path, VALUE);
			cattr.addChild(valueObj);
			valueObj.addAttribute(valueAttr);
			
			CString cstring = new CString(null, null, null, defaultValue);
			CPrimitiveObject cpo = CPrimitiveObject.createSingleRequired(path, cstring);
			valueAttr.addChild(cpo);
			
			log.debug("c_string applied on path: " + constraint.path());
		
		} else {
			// or coded_text value
			String path = ccobj.path() + "/" + VALUE; 
			CodePhrase codePhrase = parseCodePhraseAndCollectText(defaultValue, 
					path);
			CCodePhrase ccp = new CCodePhrase(path, occurrences, null, cattr, 
					null, null, codePhrase, null);
			cattr.addChild(ccp);
			
			log.debug("c_code_phrase constraint applied on path: " + constraint.path());
		}
	}
	
	protected void applyOccurrencesConstraints(Archetype archetype,
			ArchetypeConstraint constraint,	Statement rule) throws FlatteningException {
		
		BigInteger max = rule.getMax();
		BigInteger min = rule.getMin();
		
		if(max != null || min != null) {
			if(constraint instanceof CObject) {
				applyOccurrencesConstraint(archetype, (CObject) constraint, rule.getMax(),
						rule.getMin());
			}
		}		
	}
	
	protected void applyOccurrencesConstraint(Archetype archetype,
			CObject cobj, BigInteger max, BigInteger min) throws FlatteningException {
		
		log.debug("applyOccurrencesConstraint, min: " + min + ", max: " + max 
				+ ", at: " + cobj.path());
		
		if( !cobj.isRoot() ) {
			//assert(cobj.getParent() != null);
		}
		
		String path = cobj.path();
		
		Interval<Integer> occurrences = cobj.getOccurrences();
		
		// default occurrences, required [1,1]
		// special-case in archetype root, then the default occurrences
		// can be overriden
		if(( !"/".equals(path)) && occurrences == null) { 
			
			log.warn("try to set occurrences constraint on default(null)" +
					" occurrences: " + cobj.path());			
			return;			
		}
		
		if(max != null && occurrences.getUpper() != null 
				&& max.intValue() > occurrences.getUpper().intValue()) {
			throw new FlatteningException("more permissive max occurrences, " + path);
		}
		
		if(max != null && occurrences.getLower() != null
				&& max.intValue() < occurrences.getLower().intValue()) {
			throw new FlatteningException("contradicting max occurrences [max: " 
					+ max.intValue() + ", occurrences.lower: " 
					+ occurrences.getLower().intValue() + "] at path: " + path);
		}
		
		if(min != null && occurrences.getLower() != null 
				&& min.intValue() < occurrences.getLower().intValue()) {
			throw new FlatteningException("more permissive min occurrences, " + path);
		}
		
		if(min != null && occurrences.getUpper() != null
				&& min.intValue() > occurrences.getUpper().intValue()) {
			throw new FlatteningException("contradicting min occurrences, " + path);
		}
		
		// it's required already
		// special case for archetype node!
		if(( !"/".equals(path)) && occurrences.getUpper() != null && occurrences.getLower() != null
				&& occurrences.getUpper().intValue() == 1 
				&& occurrences.getLower().intValue() == 1) {
			log.warn("try to set occurrences constraint on required node: " 
					+ cobj.path());
			return;
		}		
		
		// TODO temp fix for missing min=0 when setting
		// optional occurrences in template designer
		if(min == null && max != null) {
			min = BigInteger.valueOf(0L);
		}		
		
		Integer lower = null;
		Integer upper = null;
		
		if(occurrences != null) {
			lower = occurrences.getLower();
			upper = occurrences.getUpper();
		}
		if(min != null) {
			lower = min.intValue();
			if(upper != null && lower > upper) {
				upper = lower; 
			}
		}
		if(max != null) {
			upper = max.intValue();
			if(lower != null && lower > upper) {
				lower = upper; 
			}
		}					
		
		Interval<Integer> newOccurrences = 
			new Interval<Integer>(lower, upper, lower != null, upper != null);
		
		
		log.debug("newOccurrences: " + newOccurrences);
		
		if(newOccurrences.getLower() != null 
				&& newOccurrences.getLower().intValue() > 0
				&& !"/".equals(path)) {			
			
			CAttribute parent = getParentAttribute(archetype, path);
			
			if(parent instanceof CMultipleAttribute) {
				CMultipleAttribute cma = (CMultipleAttribute) parent;
				
				log.debug("setting parent.cardinality: " + cma.getCardinality());
				
				// TODO temporarily switched off
				cma.getCardinality().getInterval().setLower(newOccurrences.getLower());
				
				log.debug("AFTER parent.cardinality: " + cma.getCardinality());
			} else {
				if(parent == null) {
					log.debug("parent null at " + cobj.path());
				}
			}
		}		
		cobj.setOccurrences(newOccurrences);
	}	
	
	// TODO add support for name-based hybrid path
	// At least for Rule-based name constraint, if the node doesn't
	// exist before, a copy of the node should be created with given name
	protected CComplexObject applyNameConstraint(Archetype archetype, 
			ArchetypeConstraint constraint, String name, String localPath) 
			throws FlatteningException {
		
		log.debug("applying name constraint [" + name + "] on path: " + 
				localPath + "constraint: " + constraint.path());
		
		// preconditions
		assert(constraint instanceof CComplexObject);
		
		CComplexObject ccobj = (CComplexObject) constraint;	
		if(name == null) {
			return ccobj;
		}
		
		//String path = ccobj.path();
		String path = localPath;
		
		if(! "/".equals(path)) {
			CAttribute parent = getParentAttribute(archetype, path);
			assert(parent != null);
			
			// add a copy of the ccobj if sibling with same node_id exists
			// this is the key to enable named-node path in template			
			if(hasSiblingNodeWithNodeId(parent, ccobj.getNodeId())) {
				removeUnnamedSiblingNode(archetype, parent, ccobj.getNodeId());
				
				ccobj = (CComplexObject) ccobj.copy();
				parent.addChild(ccobj);
				ccobj.setParent(parent);
				checkSiblingNodeIdAndName(parent, ccobj.getNodeId(), name);
				
				log.debug("sibling node with same node_id added, " + name);
			}	
	    }	
		
		// TODO check physicalPath needed
		path = ccobj.path();
		
		log.debug("applyNameConstraint - middle ccobj.path: " + ccobj.path());
		
		// perhaps unnecessary
		CAttribute nameAttr = ccobj.getAttribute(NAME);
		CPrimitiveObject cpo = null;
		
		if(nameAttr == null) {
			
			CString cstring = cString(name);			
			
			if("/".equals(path)) {
				path = path + NAME;
			} else {
				path = path+ "/" + NAME;
			}
			CComplexObject nameObj = CComplexObject.createSingleRequired(path, DV_TEXT);
			nameAttr = CSingleAttribute.createRequired(path, NAME);
			nameAttr.addChild(nameObj);
			
			path = path + "/" + VALUE;
			CAttribute valueAttr = CSingleAttribute.createRequired(path, VALUE);
			nameObj.addAttribute(valueAttr);			
			cpo = CPrimitiveObject.createSingleRequired(path, cstring);
			valueAttr.addChild(cpo);
		}
		ccobj.addAttribute(nameAttr);
		updatePathWithNamedNodeOnCObjectTree(ccobj, ccobj.getNodeId(), name);
		archetype.updatePathNodeMapRecursively(ccobj);		
		
		log.debug("after setting name, cobj.path: " + ccobj.path());
		
		return ccobj;
	}
	
	private void removeUnnamedSiblingNode(Archetype archetype,
			CAttribute parent, String nodeId) throws FlatteningException {
		
		for(Iterator<CObject> it = parent.getChildren().iterator();
				it.hasNext();) {
			CObject cobj = it.next();
			if(nodeId.equals(cobj.getNodeId())) {
				if( ! (cobj instanceof CComplexObject)) {
					throw new FlatteningException("unexpected constraint type: "
							+ cobj.getClass() + " for node_id[" + nodeId + "] at " +
							parent.path());
				}
				CComplexObject ccobj = (CComplexObject) cobj;
				
				if( ! ccobj.hasAttribute(NAME)) {
				
					it.remove();
					
					// seems unnecessary
					// archetype.reloadNodeMaps();
					
					log.debug("Unnamed sibling node[" + nodeId + "] removed..");
					
					break;
				}
			}
		}		
	}
	
	/*
	 * Check if given path is a hybrid of physical and runtime path
	 * 
	 * Example:
	 *   "/data[at0001]/items[at0002 and name/value='one']/items[at0003]"
	 */
	private boolean isHybridPath(String path) {
		// TODO validity check
		return path.indexOf(" and name") > 0;
	}
	
	// temporary solution until a proper 
	// archetype-terminology based solution is specified
	protected void __applyNameConstraint(ArchetypeConstraint constraint, 
			String name) throws FlatteningException {
		
		
	}
	
	/**
	 * Checks if there is any sibling node with the same node_id
	 * 
	 * @param parent
	 * @param nodeId
	 * @return
	 */
	private boolean hasSiblingNodeWithNodeId(CAttribute parent, String nodeId) {
		for(CObject cobj : parent.getChildren()) {
			if(nodeId.equals(cobj.getNodeId())) {
				return true;
			}
		}
		return false;
	}
	
	protected void applyValueConstraint(Archetype archetype, 
			ArchetypeConstraint constraint,	Statement rule)	
		throws FlatteningException {
		
		if(rule.getConstraint() == null) {
			return;
		}
		
		log.debug("applying value constraint on path: " + constraint.path());
		
		if( ! (constraint instanceof CComplexObject)) {
			throw new FlatteningException("Unexpected constraint type: " + 
					(constraint == null ? "null" : constraint.getClass()));
		}
		
		CComplexObject ccobj = (CComplexObject) constraint;
		if( ! ccobj.getRmTypeName().equalsIgnoreCase(ELEMENT)) {
			throw new FlatteningException("Unexpected constraint rmType: " + 
					ccobj.getRmTypeName());
		}
		ValueConstraint vc = rule.getConstraint();
		
		if(vc instanceof TextConstraint) {
			
			TextConstraint tc = (TextConstraint) vc;
			applyTextConstraint(ccobj, tc);
		
		} if(vc instanceof QuantityConstraint) {
			
			QuantityConstraint tc = (QuantityConstraint) vc;
			applyQuantityConstraint(ccobj, tc);			
			archetype.updatePathNodeMapRecursively(ccobj);
		
		} else if(vc instanceof MultipleConstraint) {
		
			MultipleConstraint mc = (MultipleConstraint) vc;
			applyMultipleConstraint(ccobj, mc);
		}
	}
	
	protected void applyQuantityConstraint(CComplexObject ccobj, 
			QuantityConstraint qc) throws FlatteningException {
		
		log.debug("applying quantity constraint on path: " + ccobj.path());
		
		String[] includedUnits = qc.getIncludedUnitsArray();
		String[] excludedUnits = qc.getExcludedUnitsArray();
		QuantityUnitConstraint[] magnitudeUnits = qc.getUnitMagnitudeArray() ;
		CDvQuantityItem item = null;
		CAttribute valueAttr = ccobj.getAttribute(VALUE);		
		String valuePath = ccobj.path() + "/" + VALUE;
		
		if(magnitudeUnits != null && magnitudeUnits.length == 1) {
			
			log.debug("setting unit_magnitude quantity constraint");
			
			QuantityUnitConstraint quc = magnitudeUnits[0];
			Interval<Double> magnitude = new Interval<Double>(
					quc.getMinMagnitude(), quc.getMaxMagnitude(), 
					quc.getIncludesMinimum(), quc.getIncludesMaximum());
			
			item = new CDvQuantityItem(magnitude, quc.getUnit());
			if(valueAttr != null) {
				valueAttr.removeAllChildren();
				CDvQuantity cdq = CDvQuantity.singleRequired(valuePath, item);
				valueAttr.addChild(cdq);	
			}
			
		} else if(includedUnits != null && includedUnits.length == 1) {
			// <constraint xsi:type="quantityConstraint">
	        //     <includedUnits>mmol/L</includedUnits>
	        // </constraint>
			
			log.debug("setting included_units quantity constraint");
			
			item = new CDvQuantityItem(includedUnits[0]);
				
		} else if(excludedUnits != null && excludedUnits.length > 0) {
			
			log.debug("setting excluded_units quantity constraint");
			
			// <constraint xsi:type="quantityConstraint">
	        //     <excludedUnits>in</excludedUnits>
	        // </constraint>
			
			if(valueAttr == null) {
				throw new FlatteningException(
						"Missing value attribute for quantityConstraint.excludedUnits");
			}
			if(valueAttr.getChildren() == null || valueAttr.getChildren().isEmpty()) {
				throw new FlatteningException(
						"Missing child obj for quantityConstraint.excludedUnits");
			}
			if(valueAttr.getChildren().size() > 1) {
				throw new FlatteningException(
						"More than one child obj for quantityConstraint.excludedUnits");
			}
			CObject child = valueAttr.getChildren().get(0);
			if( ! (child instanceof CDvQuantity)) {
				throw new FlatteningException(
						"Non-CDvQuantity child obj for quantityConstraint.excludedUnits");				
			}
			CDvQuantity cdq = (CDvQuantity) child;
			if(cdq.getList() == null || cdq.getList().isEmpty()) {
				throw new FlatteningException(
						"Empty CDvQuantity.list for quantityConstraint.excludedUnits");				
			}
			
			cdq.removeItemByUnitsList(excludedUnits);
			return;
			
		} else {
			// TODO > 1 in the array etc
			throw new FlatteningException(
					"Unsupported quantityConstraint in Template: " + qc);
		}
		
		
		if(valueAttr == null) {
		
			valueAttr = new CSingleAttribute(valuePath, VALUE, Existence.REQUIRED); 
			CDvQuantity cdq = CDvQuantity.singleRequired(valuePath, item);
			valueAttr.addChild(cdq);		
			ccobj.addAttribute(valueAttr);
		
		} else {
			
			// deal with empty c_dv_quantity to add new items			
			if(valueAttr.getChildren() == null || valueAttr.getChildren().isEmpty()) {
				throw new FlatteningException(
						"Missing child obj for quantityConstraint.excludedUnits");
			}
			if(valueAttr.getChildren().size() > 1) {
				throw new FlatteningException(
						"More than one child obj for quantityConstraint.excludedUnits");
			}
			CObject child = valueAttr.getChildren().get(0);
			if( ! (child instanceof CDvQuantity)) {
				throw new FlatteningException(
						"Non-CDvQuantity child obj for quantityConstraint.excludedUnits");				
			}
			CDvQuantity cdq = (CDvQuantity) child;
			cdq.addItem(item);
		}
	}
	
	protected void applyTextConstraint(CComplexObject ccobj, 
			TextConstraint tc) throws FlatteningException {
		
		log.debug("applying text constraint on path: " + ccobj.path());
		
		String[] includedValues = tc.getIncludedValuesArray();
		String[] excludedValues = tc.getExcludedValuesArray();
		
		CAttribute valueAttr = ccobj.getAttribute(VALUE);
		String valuePath = 	ccobj.path() + "/" + VALUE;
		String definingCodePath = valuePath + "/" + DEFINING_CODE;
		
		if(includedValues != null && includedValues.length > 0) {
			if(valueAttr == null) {				
				valueAttr = new CSingleAttribute(valuePath, VALUE, 
						Existence.REQUIRED); 
				ccobj.addAttribute(valueAttr);			
			} else if (valueAttr.getChildren().size() > 0) {			
				valueAttr.removeAllChildren();
			} 
			
			CComplexObject valueObj = CComplexObject.createSingleRequired(
					valuePath, DV_CODED_TEXT);
			valueAttr.addChild(valueObj);
			
			CAttribute definingCode = CSingleAttribute.createRequired(
					definingCodePath, DEFINING_CODE);
			valueObj.addAttribute(definingCode);			
			
			List<String> codeList = new ArrayList<String>();
			String terminology = null;
			String text = null;
			String code = null;
			for(String value : includedValues) {
				int i = value.indexOf("::");
				int j = value.lastIndexOf("::");
				if(i == j || j > value.length() - 2) {
					throw new FlatteningException(
							"wrong syntax for coded text: " + value);
				}
				terminology = value.substring(0, i);
				code = value.substring(i + 2, j);
				text = value.substring(j + 2);				
				codeList.add(code);				
				termMap.addTerm(terminology, code, text, valuePath);
			}
			
			CCodePhrase ccp = CCodePhrase.singleRequired(definingCodePath, 
					terminology, codeList);
			definingCode.addChild(ccp);
		
		} else if(excludedValues != null && excludedValues.length > 0) {
			if(valueAttr == null) {
				throw new FlatteningException(
						"failed to set excluded values on text constraint, " +
						"VALUE attribute missing");
			}
			if (valueAttr.getChildren().size() == 1) {					
				CObject child = valueAttr.getChildren().get(0);
				if(child instanceof CComplexObject) {
					CComplexObject childCCObj = (CComplexObject) child;
					if(childCCObj.getRmTypeName().equals(DV_CODED_TEXT)) {
						CAttribute definingCodeAttr = 
							childCCObj.getAttribute(DEFINING_CODE);
						
						if(definingCodeAttr == null) {
							throw new FlatteningException(
									"Missing defining_code attribute");
						}
						
						CCodePhrase ccp = 
							(CCodePhrase) definingCodeAttr.getChildren().get(0);
						
						log.debug("before codeList.size: " + ccp.getCodeList().size());
						
						for(Iterator<String> it = ccp.getCodeList().iterator(); 
								it.hasNext();) {
							String code = it.next();
							for(String value : excludedValues) {
								int i = value.indexOf("::");
								if(code.equals(value.substring(i + 2))) {
									it.remove();
								}
							}
						}
						log.debug("after codeList.size: " + ccp.getCodeList().size());
						
					} else {
						// TODO other data types?
					}
				} else {
					throw new FlatteningException(
						"Unexpected VALUE.child constrainType: " + 
								(child == null ? "null" : child.getClass()));					
				}
			} else {
				throw new FlatteningException("Unexpected VALUE.children.size: " 
						+ valueAttr.getChildren().size());
			}
		}		
	}	
	
	protected void applyMultipleConstraint(CComplexObject ccobj, 
			MultipleConstraint mc) throws FlatteningException {
		
		log.debug("applying multiple constraint on path: " + ccobj.path());
		
		String[] includedTypes = mc.getIncludedTypesArray();
		CAttribute cattr = ccobj.getAttribute(VALUE);
		
		if(cattr != null) {		
			if(cattr.getChildren() == null || cattr.getChildren().isEmpty()) {
				throw new FlatteningException(
					"failed to set value constraint, null/empty children list");
			}			
						
			List<CObject> newChildrenList = new ArrayList<CObject>();
			for(CObject child : cattr.getChildren()) {
				String childType = child.getRmTypeName();
				for(String type : includedTypes) {
					if((DATA_TYPES_PREFIX + type).equalsIgnoreCase(childType) 
							|| type.equalsIgnoreCase(childType)) {
						newChildrenList.add(child);
						break;
					}
				}														
			}
			cattr.removeAllChildren();
			for(CObject child : newChildrenList) {
				cattr.addChild(child);
			}
			log.debug("total " + newChildrenList.size() + " children left");
			
		} else {			
			// in case value attribute doesn't exist in archetype
			String valuePath = 	ccobj.path() + "/" + VALUE;
			cattr = new CSingleAttribute(valuePath, VALUE, Existence.REQUIRED); 
			ccobj.addAttribute(cattr);
			
			for(String rmType : includedTypes) {
				rmType = rmType.toUpperCase();
				if( ! rmType.startsWith("DV_")) {
					rmType = DATA_TYPES_PREFIX + rmType;
				}
				CComplexObject child = 
					CComplexObject.createSingleRequired(valuePath, rmType);
				cattr.addChild(child);
			}
			log.debug("value attribute added with total " 
					+ cattr.getChildren().size() + "child(s)");
		}		
	}
	
	/*
	 * Returns next unique nodeId in "at0001" format
	 */
	protected String formatNodeId(long count) {
		String nextId = AT + format.format(count);
		return nextId;
	}
	
	/*
	 * Gets next unique id and increase the counter
	 */
	private String nextNodeId() {
		nodeCount++;
		String nextId = formatNodeId(nodeCount);	
		//log.debug("next nodeId: " + nextId);
		return nextId;
	}
	
	/*
	 * Parse coded text like 'SNOMED-CT::258835005::mg/dygn'
	 * the last segment of actual text value 
	 * is now collected in termMap as a quick fix
	 * 
	 */
	private CodePhrase parseCodePhraseAndCollectText(String value, String path) 
			throws FlatteningException {
		
		int i = value.indexOf("::");
		int j = value.lastIndexOf("::");
		if(i == j) {
			throw new FlatteningException(
					"wrong syntax for coded text: " + value);
		}
		String terminology = value.substring(0, i);
		String code = value.substring(i + 2, j);
		String text = value.substring(j + 2);
		CodePhrase codePhrase = new CodePhrase(terminology, code);
		
		termMap.addTerm(terminology, code, text, path);		
		return codePhrase;
	}
	
	private CString cString(String value) {
		List<String> values = new ArrayList<String>();
		values.add(value);
		CString cstring = new CString(null, values, null);
		return cstring;
	}
	
	/**
	 * Finds and replaces according archetypeSlot with fully flattened cobj
	 * 
	 * @param cattr
	 * @param definition
	 * @param cobj
	 */
	void replaceSlotWithFlattened(CAttribute cattr, Archetyped definition,
			CObject cobj) {
		
		List<CObject> children = cattr.getChildren();
		if(children == null) {
			return;
		}
		for(int i = 0, j = children.size(); i < j; i++) {
			CObject child = children.get(i);
			if(child instanceof ArchetypeSlot) {
				// if the slot matches the template def
				children.remove(i);
				
				cobj.setPath(child.path());
				children.add(i, cobj);
			}
		}
	}
	
	private TEMPLATE retrieveTemplate(String templateId) 
			throws FlatteningException {
		TEMPLATE template = templateMap.get(templateId);
		if(template == null) {
			throw new UnknownTemplateException(templateId);
		}
		return template;
	}
	
	private Archetype retrieveArchetype(String archetypeId) 
			throws FlatteningException {
		
		Archetype archetype = archetypeMap.get(archetypeId);
		if(archetype == null) {
			throw new UnknownArchetypeException(archetypeId);
		}
		// make a deep copy
		archetype = archetype.copy();
		return archetype;
	}
	
	/**
	 * Find the largest node id 
	 * 
	 * @param archetype
	 * @return
	 */
	protected int findLargestNodeId(Archetype archetype) 
		throws FlatteningException {
		
		return findLargestNodeId(archetype.getDefinition(), 0);
	}
	
	// Traverse given cobj tree and find the largest node id
	protected int findLargestNodeId(CObject cobj, int largest) 
		throws FlatteningException {
		int current = largest;
		int last = 0;
		
		current = parseNodeId(cobj.getNodeID());
		if(largest > current) {
			current = largest;
		}
		if(cobj instanceof CComplexObject) {
			CComplexObject ccobj = (CComplexObject) cobj;
		
			for(CAttribute cattr : ccobj.getAttributes()) {
				for(CObject child : cattr.getChildren()) {
					last = findLargestNodeId(child, current);
					if(last > current) {
						current = last;
					}
				}
			}
		}
		return current;
	}
	
	/*
	 * Parses given nodeId and returns the integer value
	 */
	protected int parseNodeId(String nodeId) throws FlatteningException {
		if(nodeId == null) {
			return 0;
		}
		if(nodeId.length() <= AT.length()) {
			throw new FlatteningException("Too short nodeId: " + nodeId);
		}
		nodeId = nodeId.substring(AT.length());
		int dot = nodeId.indexOf(".");
		if(dot > 0) {
			nodeId = nodeId.substring(0, dot);
		} else if(dot == 0) {
			throw new FlatteningException("Bad format of nodeId: " + nodeId);
		}
		int value = 0;
		try {
			value = Integer.parseInt(nodeId);
		} catch(NumberFormatException nfe) {
			throw new FlatteningException("Bad format of nodeId: " + nodeId);
		}
		return value;
	}
	
	protected ArchetypeConstraint nodeAtHybridPath(String path) 
			throws FlatteningException {		
		return null;
	}
	
	public TermMap getTermMap() {
		return termMap;
	}	
	
	/* constant values */
	private static final String DV_TEXT = "DV_TEXT";
	private static final String DV_CODED_TEXT = "DV_CODED_TEXT";
	private static final String ELEMENT = "ELEMENT";
	private static final String NAME = "name";
	private static final String VALUE = "value";	
	private static final String ITEMS = "items";
	private static final String CONTENT = "content";
	private static final String DESCRIPTION = "description";
	private static final String DEFINING_CODE = "defining_code";
	private static final String AT = "at";
	private static final String DATA_TYPES_PREFIX = "DV_";
	
	/* static fields */
	private static DecimalFormat format = new DecimalFormat("####");
	static {
		// number of digits is not limited according to the specs
		// using 4 as minimum is by convention
		format.setMinimumIntegerDigits(4);
		format.setMaximumIntegerDigits(8);
	}
	
	private static Logger log = Logger.getLogger(Flattener.class);
	
	/* fields */
	private Map<String, Archetype> archetypeMap;
	private Map<String, TEMPLATE> templateMap;
	private TermMap termMap;
	private long nodeCount;
}
/*
 * ***** BEGIN LICENSE BLOCK ***** Version: MPL 1.1/GPL 2.0/LGPL 2.1
 * 
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the 'License'); you may not use this file except in compliance with the
 * License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an 'AS IS' basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.
 * 
 * The Original Code is Flattener.java
 * 
 * The Initial Developer of the Original Code is Rong Chen. Portions created by
 * the Initial Developer are Copyright (C) 2009-2010 the Initial Developer. All
 * Rights Reserved.
 * 
 * Contributor(s):
 * 
 * Software distributed under the License is distributed on an 'AS IS' basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.
 * 
 * ***** END LICENSE BLOCK *****
 */
